from contextlib import contextmanager
from pathlib import Path
from typing import NamedTuple

import rich
from rich.style import Style
from rich.syntax import Syntax

import lldb


class DebuggerError(Exception):
    def __init__(self, *args):
        super().__init__(*args)


@contextmanager
def _check_error():
    error = lldb.SBError()
    yield error
    if error.Fail():
        raise DebuggerError(str(error.GetCString()))


class Position(NamedTuple):
    line: int
    column: int


def _coord(text: str, index: int) -> Position:
    line = 1
    column = 0
    for i, char in enumerate(text):
        if i == index:
            return Position(line, column)
        if char == '\n':
            line += 1
            column = 0
        else:
            column += 1
    return Position(line, column)


def _print_colored(filepath: Path, code: str, start_idx: int, end_idx: int):
    start_pos = _coord(code, start_idx)
    end_pos = _coord(code, end_idx)
    s = Syntax(
        code=code,
        lexer='ts',
        line_numbers=True,
        word_wrap=True,
        background_color='default',
        highlight_lines=set(range(start_pos.line, end_pos.line + 1)),
    )
    s.stylize_range(
        style=Style(underline=True, blink=True, bgcolor='default', color='red'),
        start=start_pos,
        end=end_pos,
    )
    con = rich.get_console()
    path = filepath.relative_to(Path().resolve(), walk_up=True)
    con.print(str(path), style='repr.filename')
    con.print(s)
    if start_pos.line == end_pos.line:
        con.print('See line:', start_pos.line)
    else:
        con.print('See lines:', start_pos.line, '-', end_pos.line)


def _read_std_string_view(value: lldb.SBValue):
    address = value.GetChildMemberWithName('_M_str')
    count = value.GetChildMemberWithName('_M_len').GetValueAsUnsigned()
    data = address.GetPointeeData(0, count + 1)
    with _check_error() as error:
        return str(data.GetString(error, 0))


def _read_ark_string_view(value: lldb.SBValue):
    sv = value.GetChildMemberWithName('sv_')
    return _read_std_string_view(sv)


def _get_file_path(node: lldb.SBValue):
    ark_sv = node.EvaluateExpression('range_.program_.sourceFile_.path_')
    return Path(_read_ark_string_view(ark_sv))


@lldb.command('node_src')
def node_src(
    debugger: lldb.SBDebugger,
    command: str,
    exe_ctx: lldb.SBExecutionContext,
    result: lldb.SBCommandReturnObject,
    internal_dict: dict,
):
    rich.reconfigure(force_terminal=True, width=120)
    frame: lldb.SBFrame = exe_ctx.GetFrame()
    node = frame.EvaluateExpression(command).Dereference()
    sv = node.EvaluateExpression('range_.program_.sourceCode_.sv_')
    text = _read_std_string_view(sv)

    range = node.EvaluateExpression('range_')
    start = range.GetChildMemberWithName('startIndex_').GetValueAsUnsigned()
    end = range.GetChildMemberWithName('endIndex_').GetValueAsUnsigned()

    filepath = _get_file_path(node)

    _print_colored(filepath, text, start, end)
